package com.huawei.translate.provider;

import com.huawei.translate.config.HttpProxyConfiguration;
import com.huawei.translate.model.Renter;
import com.huawei.translate.persistence.MsAccessTokenRepository;
import com.huawei.translate.service.AuthenticationService;
import com.huawei.translate.utils.HttpUtils;
import com.huawei.translate.utils.SystemConfig;
import com.huawei.translate.vo.translate.TranslateArrayRequest;
import com.huawei.translate.vo.translate.TranslateRequest;
import com.huawei.translate.vo.translate.TranslateResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

@Service
public class MSTranslateProvider implements TranslateProvider {

    private static final Logger LOG = Logger.getLogger(MSTranslateProvider.class);

    @Autowired
    private MsAccessTokenRepository msAccessTokenRepository;

    @Autowired
    private AuthenticationService authenticationService;

    @Autowired
    private HttpProxyConfiguration proxyConfig;

    private static String accessToken = null;

    public String getSpeechAccessToken() {
        try {
            String url = SystemConfig.getProperty("mstranslate.access-token") + "?Subscription-Key=" + SystemConfig.getProperty("mspeech.subscription-key");
            return sendMsAccessTokenRequestAndParse(url);
        } catch (Exception e) {
            LOG.info("Error response get access token from ms.");
            LOG.error(e.getMessage(), e);
            return null;
        }
    }

    public String getAccessToken() {
        try {
            String url = SystemConfig.getProperty("mstranslate.access-token") + "?Subscription-Key=" + SystemConfig.getProperty("mstranslate.subscription-key");
            return sendMsAccessTokenRequestAndParse(url);
        } catch (Exception e) {
            LOG.info("Error response get access token from ms.");
            LOG.error(e.getMessage(), e);
            return null;
        }
    }

    private String sendMsAccessTokenRequestAndParse(String url) throws IOException {
        LOG.info("Get access token, going to create http client with proxy host: " + proxyConfig.getProxyHost());
        CloseableHttpClient httpClient = HttpUtils.makeHttpClient(proxyConfig.getProxyHost(), proxyConfig.getProxyPort(), proxyConfig.getProxyUserName(), proxyConfig.getProxyPassword());

        LOG.info("Receive request to get ms access token from ms.");
        HttpPost httpPost = new HttpPost(url);

        LOG.info("Start request to get ms access token from ms." + url);

        CloseableHttpResponse response = httpClient.execute(httpPost);
        String responseContent = HttpUtils.getContentFromInputStream(response.getEntity().getContent());

        if (response.getStatusLine().getStatusCode() != 200) {
            LOG.error("MS Translator: request AccessToken " + responseContent);
            return null;
        }
        LOG.info("Receive response get access token from ms.");
        return responseContent;
    }

    @Override
    public TranslateResponse translate(TranslateRequest request) {

        try {
            long startTime = System.currentTimeMillis();
            initAccessToken();
            if (accessToken == null) {
                LOG.error("Micro soft translator accessToken need been initialized");
                return null;
            }
            URI uri = createURI(request);

            String content = sendTranslateRequest(uri);


            LOG.info("Receive response from ms took:" + (System.currentTimeMillis() - startTime));

            return new TranslateResponse(HttpStatus.OK.value(), parseXmlContent(content));
        } catch (Exception e) {
            LOG.error("Happened Exception Request:" + request.toString() + "\t info:" + e.getStackTrace().toString());
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String translate(String from, String to, String content, String key) {
        try {
            initAccessToken();

            URI uri = createURI(from, to, content, key);
            String translateResult = sendTranslateRequest(uri);
            LOG.info("TranslateResult" + translateResult);
            return parseXmlContent(translateResult);

        } catch (Exception e) {
            return null;
        }
    }

    private String sendTranslateRequest(URI uri) throws IOException {
        LOG.info("translate, going to create http client with proxy host: " + proxyConfig.getProxyHost());
        CloseableHttpClient httpClient = HttpUtils.makeHttpClient(proxyConfig.getProxyHost(), proxyConfig.getProxyPort(), proxyConfig.getProxyUserName(), proxyConfig.getProxyPassword());


        HttpGet httpGet = new HttpGet(uri);

        httpGet.setConfig(RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000)
                .build());

        LOG.info("Start request to translate from ms." + httpGet.getURI());
        CloseableHttpResponse response = httpClient.execute(httpGet);

        InputStream inputStream = response.getEntity().getContent();
        String content = HttpUtils.getContentFromInputStream(inputStream);
        LOG.info("Receive request to translate from ms.");
        LOG.info("ms translate content is:" + content);
        if (response.getStatusLine().getStatusCode() != 200) {
            LOG.error("MS Translator: request translate http code " + response.getStatusLine().getStatusCode());
            LOG.error("error Message:" + content);
            return null;
        }
        return content;
    }

    public List<String> translate(TranslateArrayRequest request) {
        try {
            long startTime = System.currentTimeMillis();
            initAccessToken();
            CloseableHttpClient httpClient = HttpUtils.makeHttpClient(proxyConfig.getProxyHost(), proxyConfig.getProxyPort(), proxyConfig.getProxyUserName(), proxyConfig.getProxyPassword());

            String url = SystemConfig.getProperty("mstranslate.translate.array");

            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/xml");
            String xml = buildArrayRequestBody(request);
            LOG.info("Build request body took:" + (System.currentTimeMillis() - startTime));
            startTime = System.currentTimeMillis();
            httpPost.setConfig(RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000)
                    .build());
            HttpEntity entity = new ByteArrayEntity(xml.getBytes("UTF-8"));
            httpPost.setEntity(entity);

            CloseableHttpResponse response = httpClient.execute(httpPost);

            InputStream inputStream = response.getEntity().getContent();
            String translateResult = HttpUtils.getContentFromInputStream(inputStream);
            LOG.info("TranslateResult" + translateResult);

            if (response.getStatusLine().getStatusCode() != 200) {
                LOG.error("MS Translator: request translate http code " + response.getStatusLine().getStatusCode());
                LOG.error("error Message:" + translateResult);
                return null;
            }
            LOG.info("Receive response from ms took:" + (System.currentTimeMillis() - startTime));
            return parseXmlArray(translateResult);
        } catch (Exception e) {
            LOG.error("Happened Exception Request info:" + e.getStackTrace().toString());
            e.printStackTrace();
            return null;
        }
    }

    private String buildArrayRequestBody(TranslateArrayRequest request) {
        Renter renter = authenticationService.parseRenterFromAccessToken(request.getAccessToken());
        StringBuilder requestXmlBuilder = new StringBuilder();
        StringBuilder textBuilder = new StringBuilder();
        for (String text : request.getTexts()) {
            textBuilder.append("<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">")
                        .append(text)
                        .append("</string>");
        }
        if (StringUtils.isEmpty(renter.getCategory())) {
            return  requestXmlBuilder
                    .append("<TranslateArrayRequest><AppId>Bearer ")
                    .append(accessToken)
                    .append("</AppId><From>")
                    .append(request.getFrom())
                    .append("</From><Texts>")
                    .append(textBuilder.toString())
                    .append("</Texts><To>")
                    .append(request.getTo())
                    .append("</To></TranslateArrayRequest>").toString();
        } else {
            return requestXmlBuilder
                    .append("<TranslateArrayRequest><AppId>Bearer ")
                    .append(accessToken)
                    .append("</AppId><From>")
                    .append(request.getFrom())
                    .append("</From><Options>")
                    .append("<Category xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2\" >")
                    .append("renter.getCategory()")
                    .append("</Category></Options><Texts>")
                    .append(textBuilder.toString())
                    .append("</Texts><To>")
                    .append(request.getTo())
                    .append("</To></TranslateArrayRequest>").toString();
        }
    }

    public String detect(String content) {
        try {
            initAccessToken();

            if (accessToken == null) {
                LOG.error("Micro soft translator accessToken need been initialized");
                return null;
            }
            LOG.info("Detect, going to create http client with proxy host: " + proxyConfig.getProxyHost());
            CloseableHttpClient httpClient = HttpUtils.makeHttpClient(proxyConfig.getProxyHost(), proxyConfig.getProxyPort(), proxyConfig.getProxyUserName(), proxyConfig.getProxyPassword());


            URIBuilder builder = new URIBuilder(SystemConfig.getProperty("mstranslate.detect"));

            builder.setParameter("appid", "Bearer " + accessToken)
                    .setParameter("text", content);
            HttpGet httpGet = new HttpGet(builder.build());

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
            httpGet.setConfig(requestConfig);

            CloseableHttpResponse response = httpClient.execute(httpGet);

            InputStream inputStream = response.getEntity().getContent();
            String responseContent = HttpUtils.getContentFromInputStream(inputStream);
            if (response.getStatusLine().getStatusCode() != 200) {
                LOG.error("Language detect Error" + responseContent);
                return null;
            }
            return parseXmlContent(responseContent);
        } catch (Exception e) {
            LOG.error(e.getMessage());
            return null;
        }
    }

    public URI createURI(TranslateRequest request) {
        try {
            Renter renter = authenticationService.parseRenterFromAccessToken(request.getAccessToken());

            URIBuilder builder = new URIBuilder(SystemConfig.getProperty("mstranslate.translate"));
            builder.setParameter("appid", "Bearer " + accessToken)
                    .setParameter("form", request.getFrom())
                    .setParameter("to", request.getTo())
                    .setParameter("text", request.getText());
            if (StringUtils.isEmpty(renter.getCategory())) {
                builder.setParameter("category", "generalNN");
            } else {
                builder.setParameter("category", renter.getCategory());
            }
            return builder.build();
        } catch (URISyntaxException e) {
            LOG.error(e.getMessage());
            return null;
        }
    }

    public URI createURI(String from, String to, String text, String category) {
        try {
            URIBuilder builder = new URIBuilder(SystemConfig.getProperty("mstranslate.translate"));
            builder.setParameter("appid", "Bearer " + accessToken)
                    .setParameter("form", from)
                    .setParameter("to", to)
                    .setParameter("text", text)
                    .setParameter("category", category);
            return builder.build();
        } catch (URISyntaxException e) {
            LOG.error(e.getMessage());
            return null;
        }
    }

    protected void initAccessToken() {
        if (msAccessTokenRepository.findAllByOrderByUpdatedAtDesc().size() > 0) {
            accessToken = msAccessTokenRepository.findAllByOrderByUpdatedAtDesc().get(0).getAccessToken();
        }
    }

    @Override
    public String toDescription() {
        return TranslateProviderType.MS.getDescription();
    }

    private String parseXmlContent(String xml) throws Exception {
        Document document = DocumentHelper.parseText(xml);
        return document.getRootElement().getText();
    }

    private List<String> parseXmlArray(String xml) throws DocumentException {
        List<String> rs = new ArrayList<>();
        Document document = DocumentHelper.parseText(xml);
        Element root = document.getRootElement();
        List<Element> elements = root.elements("TranslateArrayResponse");
        for (Element el : elements) {
            rs.add(el.element("TranslatedText").getText());
        }
        return rs;
    }
}
